# pyscribd.py
# Author: Navtej Singh (n  [d0t] s [d0t] buttar @ gmail [d0t]  com)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Scribd documentation - http://www.scribd.com/developers/api


import xml
from xmlnode import XMLNode
from urllib import urlencode,urlopen
import sys, urllib2
import mimetools, mimetypes
import os, stat
from cStringIO import StringIO
from xml.sax.saxutils import escape

class ScribdException(Exception) :  pass


#shamelessly copied from pyslideshare code
class Callable:
    def __init__(self, anycallable):
        self.__call__ = anycallable


class MultipartPostHandler(urllib2.BaseHandler):
    handler_order = urllib2.HTTPHandler.handler_order - 10 # needs to run first

    def http_request(self, request):
        data = request.get_data()
        if data is not None and type(data) != str:
            v_files = []
            v_vars = []
            try:
                 for(key, value) in data.items():
                     if type(value) == file:
                         v_files.append((key, value))
                     else:
                         v_vars.append((key, value))
            except TypeError:
                systype, value, traceback = sys.exc_info()
                raise TypeError, "not a valid non-string sequence or mapping object", traceback

            if len(v_files) == 0:
                data = urllib.urlencode(v_vars, doseq)
            else:
                boundary, data = self.multipart_encode(v_vars, v_files)

                contenttype = 'multipart/form-data; boundary=%s' % boundary
                if(request.has_header('Content-Type')
                   and request.get_header('Content-Type').find('multipart/form-data') != 0):
                    print "Replacing %s with %s" % (request.get_header('content-type'), 'multipart/form-data')
                request.add_unredirected_header('Content-Type', contenttype)
            request.add_data(data)       
        return request

    def multipart_encode(vars, files, boundary = None, buf = None):
        if boundary is None:
            boundary = mimetools.choose_boundary()
        if buf is None:
            buf = StringIO()
        for(key, value) in vars:
            buf.write('--%s\r\n' % boundary)
            buf.write('Content-Disposition: form-data; name="%s"' % key)
            buf.write('\r\n\r\n' + str(value) + '\r\n')
        for(key, fd) in files:
            file_size = os.fstat(fd.fileno())[stat.ST_SIZE]
            filename = fd.name.split('/')[-1]
            contenttype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
            buf.write('--%s\r\n' % boundary)
            buf.write('Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (key, filename))
            buf.write('Content-Type: %s\r\n' % contenttype)
            # buffer += 'Content-Length: %s\r\n' % file_size
            fd.seek(0)
            buf.write('\r\n' + fd.read() + '\r\n')
        buf.write('--' + boundary + '--\r\n\r\n')
        buf = buf.getvalue()
        return boundary, buf
    multipart_encode = Callable(multipart_encode)
    https_request = http_request


#inefficient but since we chang ethe working data, good to go , need to make it better
def cleanse_cdata(data):
    working_data=data
    while True:
        cdata_start=working_data.find("<![CDATA[")
        if cdata_start < 0 : break
        text_start=cdata_start+len("<![CDATA[")
        cdata_end=cdata_start+working_data[cdata_start:].find("]]>")
        text_end=cdata_end
        working_data=working_data[:cdata_start]+escape(working_data[text_start:text_end])+working_data[cdata_end+len("]]>"):]
    return working_data

#def DEBUG(text):
#    if ScribdAPI.#DEBUG:
#        print "=="*20
#        print text
#        print "=="*20

def Sanitize(string):
    illegal=[0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0xb,0xc,0xe,0xf,0x10,0x11,0x12,0x13,0x14 , 0x15 , 0x16 , 0x17 , 0x18 , 0x19 , 0x1a , 0x1b , 0x1c , 0x1d , 0x1e , 0x1f,0x7f]
    return "".join([i for i in string if ord(i) not in illegal])

class Document:
    def __init__(self):
        self.id=None
        self.secret=None
        self.access_key=None
        self.title=None
        self.description=None
        self.conversion_status=None
        self.public=0
        self.tags=None
        self.show_ads=None
        self.license=None
        self.thumbnail_url=None
        self.linkback_url=None
        self.page_count=None
    
    def SetSettings(self):
       sapi=ScribdAPI()
       args=dict()
       args['doc_ids']=self.id

       if self.title:
        args['title']=self.title

       if self.description:
        args['description']=self.description

       if self.public:
        args['access']='public'
       else:
        args['access']='private'

       if self.license:
        args['license']=self.license

       if self.show_ads:
        args['show_ads']=self.show_ads

       if self.linkback_url:
        args['link_back_url']=self.self.linkback_url

       sapi.ChangeSettings(**args)


    def _dump_details(self):
        print "[ID : %s] [Secret :%s] [AccessKey : %s] [ConversionStatus : %s] [Public? : %s] [Ads? %s] [Lic : %s]"%(self.id,self.secret,self.access_key,self.conversion_status,self.public,self.show_ads,self.license)
        print "[Tags : %s]"%(self.tags)
        print "[Thumbnail URL  : %s]"%(self.thumbnail_url)
        print "[LinkBack URL  : %s]"%(self.linkback_url)
        print "[Title : %s]"%(self.title)
        print "[Description : %s]"%(self.description)
        print "[Pages : %s]"%(self.page_count)

    def _parse_response(self,result):
            self.id=result.doc_id[0].text.strip()
            self.title=((result.title[0].text).strip()).encode('ascii','replace')
            self.description=((result.description[0].text).strip()).encode('ascii','replace')
            try:
                self.conversion_status=result.conversion_status[0].text.strip()
            except:
                pass
            try:
                self.access_key=result.access_key[0].text.strip()
            except:
                pass
            try:
                self.secret=result.secret_password[0].text.strip()
            except:
                pass
            try:
                if result.access[0].text.strip() == 'public':
                    self.public=1
            except:
                pass
            try:
                #DEBUG(result.tags[0])
                self.tags=result.tags[0].text.strip().encode('ascii','replace')
            except:
                pass
            try:
                self.show_ads=result.show_ads[0].text.strip()
            except:
                pass
            try:
                self.license=result.license[0].text.strip()
            except:
                pass
            try:
                self.thumbnail_url=result.thumbnail_url[0].text.strip()
            except:
                pass
            try:
                self.linkback_url=result.link_back_url[0].text.strip()
            except:
                pass
            try:
                self.page_count=int(result.page_count[0].text.strip())
            except:
                pass

class ScribdAPI:
    host='http://api.scribd.com/api'
    api_key=''
    #DEBUG=True

    def __init__(self,username='',passwd=''):
        self.session_key=None
        self.userid=None
        if username and passwd:
            self.Login(username,passwd)
        
    def __getattr__(self, attrib):
        if attrib.startswith('_'):
            raise AttributeError("No such attribute '%s'" % attrib)
        
        method = attrib.replace("_", ".")
        
        def handler(*args,**kwargs):
            #DEBUG("In generic handler\nargs=%s\nkwargs:%s"%(args,kwargs))
            if not ScribdAPI.api_key:
                raise ScribdException, "Please set the API key"

            kwargs['method']=method
            kwargs['api_key']=ScribdAPI.api_key
            if self.session_key:
                kwargs['session_key']=self.session_key
            if self.userid:
                kwargs['user_id']=self.userid
            return self.__call_method(*args,**kwargs)
        
        return handler
    
    def __call_method(self,*args,**kwargs):
        #DEBUG("In __call_method\nargs=%s\nkwargs:%s"%(args,kwargs))
        url="%s?%s"%(ScribdAPI.host,urlencode(kwargs))
        #DEBUG("In __call_method :\nURL:%s "%url)
        data = urlopen(url).read()
        data=Sanitize(cleanse_cdata(data))
        #DEBUG("In __call_method :\nRAW RSP:\n%s "%data)
        response = XMLNode.parse(data)

        if response['stat'] != 'ok':
            raise ScribdException, "Error Code : %s, Text : %s "%(response.error[0]['code'],response.error[0]['message'])
        return response

    def Login(self,username,passwd):
        rsp=self.user_login(username=username,password=passwd)
        self.session_key=rsp.session_key[0].text
        self.userid=rsp.user_id[0].text
    
    def URLupload(self,url,public=False):
        if public:
            access='public'
        else:
            access='private'
        rsp=self.docs_uploadFromUrl(url=url,access=access)
        rt=list()
        rt.append(rsp.doc_id[0].text)
        rt.append(rsp.access_key[0].text)
        if 'secret_password' in dir(rsp):
            rt.append(rsp.secret_password[0].text)
        return rt
        
    def Upload(self,fname,public=False):
        if public:
            access='public'
        else:
            access='private'
        
        if not ScribdAPI.api_key:
            raise ScribdException, "Please set the API key"

        if (not self.session_key) or (not self.userid):
            raise ScribdException, "Please login first"

        opener = urllib2.build_opener(MultipartPostHandler) 
        params={"access":access,"file" : open(fname,"rb"),"method":"docs.upload","api_key":ScribdAPI.api_key,"session_key":self.session_key,"user_id":self.userid}
        data = opener.open("http://api.scribd.com/api", params).read()

        data=Sanitize(data)
        response = XMLNode.parse(data)

        if response['stat'] != 'ok':
            raise ScribdException, "Error Code : %s, Text : %s "%(response.error[0]['code'],response.error[0]['message'])

        rt=list()
        rt.append(response.doc_id[0].text)
        rt.append(response.access_key[0].text)
        if 'secret_password' in dir(response):
            rt.append(response.secret_password[0].text)
        return rt

    def GetDocumentList(self):
        rsp=self.docs_getList()
        documents=list()
        for result in rsp.resultset[0].result:
            #DEBUG(result)
            doc=Document()
            doc._parse_response(result)
            documents.append(doc)
        return documents         

    def GetConversionStatus(self,docid):
        rsp=self.docs_getConversionStatus(doc_id=docid) 
        return rsp.conversion_status[0].text.strip()

    def GetDownloadLink(self,docid,type='original'):
        rsp=self.docs_getDownloadUrl(doc_id=docid,doc_type=type) 
        return rsp.download_link[0].text.strip()

    def GetSettings(self,docid):
        rsp=self.docs_getSettings(doc_id=docid)
        doc=Document()
        doc._parse_response(rsp)
        return doc
    
    def ChangeSettings(self,**args):
        docs=",".join((map(str,list(args['doc_ids']))))
        if args.has_key('tags'): args['tags']=args['tags'].replace(' ',',' )
        self.docs_changeSettings(**args)

    def Delete(self,docid):
        rsp=self.docs_delete(doc_id=docid) 

    def Search(self,query,start=1,max=1000,scope='all'):
        rsp=self.docs_search(query=query,num_start=start,num_results=max,scope=scope) 
        documents=list()
        for result in rsp.result_set[0].result:
            #DEBUG(result)
            doc=Document()
            doc._parse_response(result)
            documents.append(doc)
        return documents         

if __name__=="__main__":
    username=sys.argv[1]
    password=sys.argv[2]
    ScribdAPI.api_key=sys.argv[3]
    s=ScribdAPI(username,password)
    rt=s.GetDocumentList()
    for r in rt:
        #print "(%s) %s\n\tPDF: %s\n\tTXT: %s\n\tOriginal: %s"%(r.id,r.title,s.GetDownloadLink(r.id,"pdf"),s.GetDownloadLink(r.id,"txt"),s.GetDownloadLink(r.id))
        r._dump_details()
    print
